/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.diagram.statemachine.command;

import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.gef.commands.Command;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Region;

import com.cea.papyrus.core.helper.NodeHelper;
import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.diagram.statemachine.helper.NodeRegionHelper.Zone;
import com.cea.papyrus.diagraminterchange2.di2.Di2Factory;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;
import com.cea.papyrus.diagraminterchange2.di2.Property;
import com.cea.papyrus.diagraminterchange2.di2.Uml1SemanticModelBridge;

/**
 * A command to specifically add graph nodes attached to a region.
 *  
 * @author David Servat
 * @since 29 jan. 07
 */
public class AddRegionNodeCommand extends Command{
	
	/** Debug channel. */
	private int channel = 501;

	/** The region to add. */
	private GraphNode childRegion = null;
	
	/** Previous dropLocation. */
	private String previousDropLocation = "";
	
	/** New dropLocation. */
	private String newDropLocation = "";
	
	/** The parent region, i.e. the region where we want to add a new one */
	private GraphNode parentRegion = null;
	
	/** DeleteRegionCommands used when drag and droping. */
	private DeleteRegionNodeCommand deleteRegionFromSourceRegionCommand = null;
	private DeleteRegionNodeCommand deleteRegionFromTargetRegionCommand = null;

	/**
	 * A constructor used when the mouse pointer location is known.
	 * This mouse location is used to compute a dropLocation for the region.
	 * 
	 * @param child region to add
	 * @param parent region where we add
	 * @param location of mouse pointer
	 * 
	 */
	public AddRegionNodeCommand(GraphNode child, GraphNode parent, Point location){
		Debug.debug(this, "call AddRegionNodeCommand(GraphNode child, GraphNode parent, Point location)", channel);
		
		//a bunch of precautionary tests
		if((child == null) || (parent == null))
			throw new IllegalArgumentException();

		if((child.getSemanticModel() == null) || (parent.getSemanticModel() == null))
			throw new IllegalArgumentException();

		Element elt1 = ((Uml1SemanticModelBridge)child.getSemanticModel()).getElement();
		Element elt2 = ((Uml1SemanticModelBridge)parent.getSemanticModel()).getElement();
		if((elt1 == null) || (elt2 == null))
			throw new IllegalArgumentException();

		if(!(elt1 instanceof Region) || !(elt2 instanceof Region))
			throw new IllegalArgumentException();
		
		//now we can store them
		childRegion = child;
		parentRegion = parent;
		
		//store previous dropLocation
		previousDropLocation = childRegion.getProperty("dropLocation").getValue();
		//if we have a relevant position coming from the drag and drop
		//we can assign a specific drop location for the new Region
		if(location != null){
			Dimension d = parentRegion.getSize();
			//Note: comparing with computation done elsewhere (e.g. in StateMachineLayoutEditPolicy)
			//the equations are changed because the mouseLocation is given relative to the parent here!
			//take the equations elsewhere and set the equivalent of parent position to 0
			//d1 is for the first diagonal (going up)d
			double d1 = location.y+1.0*d.height*location.x/d.width-d.height;
			//d2 is for the second (going down)
			double d2 = location.y-1.0*d.height*location.x/d.width;
			if((d1<=0) && (d2<=0)){
				//TOP
				newDropLocation = Zone.TOP;
			}
			else if((d1<=0) && (d2>0)){
				//LEFT
				newDropLocation = Zone.LEFT;
			}
			else if((d1>0) && (d2<=0)){
				//RIGHT
				newDropLocation = Zone.RIGHT;
			}
			else if((d1>0) && (d2>0)){
				//BOTTOM
				newDropLocation = Zone.BOTTOM;
			}
		}
	}

	/**
	 * The constructor used when no mouse location is known.
	 * The dropLocation is supposed to be given by the child node itself.
	 * 
	 * @param child region to add
	 * @param parent region where we add
	 * 
	 */
	public AddRegionNodeCommand(GraphNode child, GraphNode parent){
		Debug.debug(this, "call AddRegionNodeCommand(GraphNode child, GraphNode parent)", channel);
		
		//a bunch of precautionary tests
		if((child == null) || (parent == null))
			throw new IllegalArgumentException();

		if((child.getSemanticModel() == null) || (parent.getSemanticModel() == null))
			throw new IllegalArgumentException();

		Element elt1 = ((Uml1SemanticModelBridge)child.getSemanticModel()).getElement();
		Element elt2 = ((Uml1SemanticModelBridge)parent.getSemanticModel()).getElement();
		if((elt1 == null) || (elt2 == null))
			throw new IllegalArgumentException();

		if(!(elt1 instanceof Region) || !(elt2 instanceof Region))
			throw new IllegalArgumentException();
		
		//now we can store them
		childRegion = child;
		parentRegion = parent;
		
		//store new dropLocation
		newDropLocation = childRegion.getProperty("dropLocation").getValue();
	}

	/** 
	 * Stores previous dropLocation and calls the redo() method.
	 */
	@Override
	public void execute(){
		Debug.debug(this, "call AddRegionNodeCommand.execute()", channel);

		redo();
	}

	/**
	 * Does the job of adding a child region on a parent region
	 */
	@Override
	public void redo(){
		Debug.debug(this, "call AddRegionNodeCommand.redo()", channel);

		//retrieve infos from child node
		Property childZoneProperty = (Property)childRegion.getProperty("zone");
		String childZone = childZoneProperty.getValue();
		//sets the new dropLocation
		Property childDropLocationProperty = (Property)childRegion.getProperty("dropLocation");
		childDropLocationProperty.setValue(newDropLocation);
		String childDropLocation = childDropLocationProperty.getValue();

		GraphNode stateMachineOrStateNode = null;
		
		//check case when drag and droping an already created region
		if(childRegion.getSemanticParent() != null){
			stateMachineOrStateNode = (GraphNode)childRegion.getSemanticParent();
			//we need to remove the childRegion first
			if(deleteRegionFromSourceRegionCommand == null)
				deleteRegionFromSourceRegionCommand = new DeleteRegionNodeCommand(childRegion);
			deleteRegionFromSourceRegionCommand.execute();
		}

		//the semantic parent node: either a state machine or a state
		stateMachineOrStateNode = (GraphNode)parentRegion.getSemanticParent();
		
		//retrieve the parent zone
		Property parentZoneProperty = (Property)parentRegion.getProperty("zone");
		String parentZone = parentZoneProperty.getValue();

		//create a splitter graph node
		//assign size and position of parent region to the spitter before any update 
		GraphNode splitter = new NodeHelper().createGraphElement(null, null, parentRegion.getPosition().getCopy(), parentRegion.getSize().getCopy());
		//prepare a property to hold the type of splitter 
		Property splitterProperty = Di2Factory.eINSTANCE.createProperty();
		splitterProperty.setKey("zone");
		//the splitter will copy the parent zone
		splitterProperty.setValue(parentZone);
		splitter.getProperty().add(splitterProperty);

		//assign stateMachineOrStateNode as the semantic parent to both splitter and new region nodes
		childRegion.setSemanticParent(stateMachineOrStateNode);
		splitter.setSemanticParent(stateMachineOrStateNode);

		//retrieve the container of parent region
		GraphNode virtualParent = (GraphNode)parentRegion.getContainer();

		
		//test the type of the child drop location
		
		//dirty fix in case no dropLocation was given, defaulted to RIGHT
		if(Zone.isNone(childDropLocation))
			childDropLocation = Zone.RIGHT;
		
		//the test itself
		if(Zone.isRight(childDropLocation)){
			//the child zone should reflect that of parent
			childZone = Zone.setRight(Zone.copy(parentZone));
			childZoneProperty.setValue(childZone);
			//the parent zone must be updated
			parentZone = Zone.setLeft(parentZone);
			parentZoneProperty.setValue(parentZone);
			//shrink the parent region in the horizontal axis
			parentRegion.getSize().scale(0.5, 1.0);
			//assign updated size of parent region to child region
			childRegion.setSize(parentRegion.getSize().getCopy());
			//assign a translated position to child region after shrinking
			childRegion.setPosition(parentRegion.getPosition().getTranslated(parentRegion.getSize().width, 0));
		}
		else if(Zone.isLeft(childDropLocation)){
			//the child zone should reflect that of parent
			childZone = Zone.setLeft(Zone.copy(parentZone));
			childZoneProperty.setValue(childZone);
			//the parent zone must be updated
			parentZone = Zone.setRight(parentZone);
			parentZoneProperty.setValue(parentZone);
			//shrink the parent region in the horizontal axis
			parentRegion.getSize().scale(0.5, 1.0);
			//assign updated size of parent region to child region
			childRegion.setSize(parentRegion.getSize().getCopy());
			//assign parent position to child region
			childRegion.setPosition(parentRegion.getPosition().getCopy());
			//assign a translated position to parent region after shrinking
			parentRegion.setPosition(parentRegion.getPosition().getTranslated(parentRegion.getSize().width, 0));
		}
		else if(Zone.isBottom(childDropLocation)){
			//the child zone should reflect that of parent
			childZone = Zone.setBottom(Zone.copy(parentZone));
			childZoneProperty.setValue(childZone);
			//the parent zone must be updated
			parentZone = Zone.setTop(parentZone);
			parentZoneProperty.setValue(parentZone);
			//shrink the parent region in the vertical axis
			parentRegion.getSize().scale(1.0, 0.5);
			//assign updated size of parent region to child region
			childRegion.setSize(parentRegion.getSize().getCopy());
			//assign a translated position to child region
			childRegion.setPosition(parentRegion.getPosition().getTranslated(0, parentRegion.getSize().height));
		}
		else if(Zone.isTop(childDropLocation)){
			//the child zone should reflect that of parent
			childZone = Zone.setTop(Zone.copy(parentZone));
			childZoneProperty.setValue(childZone);
			//the parent zone must be updated
			parentZone = Zone.setBottom(parentZone);
			parentZoneProperty.setValue(parentZone);				
			//shrink the parent region in the vertical axis
			parentRegion.getSize().scale(1.0, 0.5);
			//assign updated size of parent region to child region
			childRegion.setSize(parentRegion.getSize().getCopy());
			//assign parent position to  child region
			childRegion.setPosition(parentRegion.getPosition().getCopy());			
			//assign a translated position to parent region
			parentRegion.setPosition(parentRegion.getPosition().getTranslated(0, parentRegion.getSize().height));
		}

		//rearrange the node connection
		//the order seems important because of the various listeners involved
		//remove old content, set container then add new content
		virtualParent.getContained().remove(parentRegion);
		splitter.getContained().add(parentRegion);
		splitter.getContained().add(childRegion);
		virtualParent.getContained().add(splitter);
	}

	/**
	 * Performs the undo then restores the previous dropLocation.
	 * 
	 */
	public void undo(){
		Debug.debug(this, "call AddRegionNodeCommand.undo()", channel);
		
		//first delete the child region from target region
		if(deleteRegionFromTargetRegionCommand == null)
			deleteRegionFromTargetRegionCommand = new DeleteRegionNodeCommand(childRegion);
		deleteRegionFromTargetRegionCommand.execute();
		
		//then perform an undo on the deletion of the child region from its source
		if(deleteRegionFromSourceRegionCommand != null)
			deleteRegionFromSourceRegionCommand.undo();
			
		//reset the previous dropLocation
		childRegion.getProperty("dropLocation").setValue(previousDropLocation);
	}
}
